#ifndef __STRPOOL_H__
#define __STRPOOL_H__

/*
===============================================================================

TStrPool

===============================================================================
*/

namespace inf
{
    template <typename T>
    class TStrPool;

    template <typename T>
    class TPoolStr : public TStr<T> {
        friend class TStrPool<T>;

    public:
        TPoolStr() { numUsers = 0; }
        ~TPoolStr() { assert( numUsers == 0 ); }

        // returns total size of allocated memory
        size_t				allocated( void ) const { return TStr<T>::allocated(); }
        // returns total size of allocated memory including size of string pool type
        size_t				total_size( void ) const { return sizeof( *this ) + allocated(); }
        // returns a pointer to the pool this string was allocated from
        TStrPool<T>*			get_pool( void ) const { return pool; }

    private:
        TStrPool<T> *			pool;
        mutable int			numUsers;
    };

    template <typename T>
    class TStrPool {
    public:
        TStrPool( int newgranularity = 16 ) {
            caseSensitive = true;
            pool.set_granularity( newgranularity );
        }

        void				set_case_sensitive( bool caseSensitive );

        int					size( void ) const { return pool.size(); }
        size_t				allocated( void ) const;
        size_t				total_size( void ) const;

        const TPoolStr<T> *	    operator[]( int index ) const { return pool[index]; }

        const TPoolStr<T> *	alloc_string( const T* string );
        void				free_string( const TPoolStr<T> *poolStr );
        const TPoolStr<T> *	copy_string( const TPoolStr<T> *poolStr );
        void				clear( void );

    private:
        bool				caseSensitive;
        List<TPoolStr<T>*>	    pool;
        HashIndex			poolHash;
    };

    /*
    ================
    TStrPool::set_case_sensitive
    ================
    */
    template <typename T>
    void TStrPool<T>::set_case_sensitive( bool caseSensitive ) {
        this->caseSensitive = caseSensitive;
    }

    /*
    ================
    TStrPool::alloc_string
    ================
    */
    template <typename T>
    const TPoolStr<T> *TStrPool<T>::alloc_string( const T *string ) {
        int i, hash;
        TPoolStr<T> *poolStr;

        hash = poolHash.generate_key( string, caseSensitive );
        if ( caseSensitive ) {
            for ( i = poolHash.get_first( hash ); i != HashIndex::NULL_INDEX; i = poolHash.get_next( i ) ) {
                if ( pool[i]->cmp( string ) == 0 ) {
                    pool[i]->numUsers++;
                    return pool[i];
                }
            }
        } else {
            for ( i = poolHash.get_first( hash ); i != HashIndex::NULL_INDEX; i = poolHash.get_next( i ) ) {
                if ( pool[i]->cmp( string, false ) == 0 ) {
                    pool[i]->numUsers++;
                    return pool[i];
                }
            }
        }

        poolStr = new TPoolStr<T>;
        *static_cast<TStr<T> *>(poolStr) = string;
        poolStr->pool = this;
        poolStr->numUsers = 1;
        poolHash.add( hash, pool.append( poolStr ) );
        return poolStr;
    }

    /*
    ================
    TStrPool::free_string
    ================
    */
    template <typename T>
    void TStrPool<T>::free_string( const TPoolStr<T> *poolStr ) {
        int i, hash;

        assert( poolStr->numUsers >= 1 );
        assert( poolStr->pool == this );

        poolStr->numUsers--;
        if ( poolStr->numUsers <= 0 ) {
            hash = poolHash.generate_key( poolStr->c_str(), caseSensitive );
            if ( caseSensitive ) { 
                for ( i = poolHash.get_first( hash ); i != HashIndex::NULL_INDEX; i = poolHash.get_next( i ) ) {
                    if ( pool[i]->cmp( poolStr->c_str() ) == 0 ) {
                        break;
                    }
                }
            } else {
                for ( i = poolHash.get_first( hash ); i != HashIndex::NULL_INDEX; i = poolHash.get_next( i ) ) {
                    if ( pool[i]->cmp( poolStr->c_str(), false ) == 0 ) {
                        break;
                    }
                }
            }
            assert( i != -1 );
            assert( pool[i] == poolStr );
            delete pool[i];
            pool.remove_index( i );
            poolHash.remove_index( hash, i );
        }
    }

    /*
    ================
    TStrPool::copy_string
    ================
    */
    template <typename T>
    const TPoolStr<T> *TStrPool<T>::copy_string( const TPoolStr<T> *poolStr ) {

        assert( poolStr->numUsers >= 1 );

        if ( poolStr->pool == this ) {
            // the string is from this pool so just increase the user count
            poolStr->numUsers++;
            return poolStr;
        } else {
            // the string is from another pool so it needs to be re-allocated from this pool.
            return alloc_string( poolStr->c_str() );
        }
    }

    /*
    ================
    TStrPool::clear
    ================
    */
    template <typename T>
    void TStrPool<T>::clear( void ) {
        int i;

        for ( i = 0; i < pool.size(); i++ ) {
            pool[i]->numUsers = 0;
        }
        delete_contents(pool, true);
        poolHash.free();
    }

    /*
    ================
    TStrPool::allocated
    ================
    */
    template <typename T>
    size_t TStrPool<T>::allocated( void ) const {
        int i;
        size_t size;

        size = pool.allocated() + poolHash.allocated();
        for ( i = 0; i < pool.size(); i++ ) {
            size += pool[i]->allocated();
        }
        return size;
    }

    /*
    ================
    TStrPool::total_size
    ================
    */
    template <typename T>
    size_t TStrPool<T>::total_size( void ) const {
        int i;
        size_t size;

        size = pool.total_size() + poolHash.total_size();
        for ( i = 0; i < pool.size(); i++ ) {
            size += pool[i]->total_size();
        }
        return size;
    }

    typedef TPoolStr<char> PoolStr;
    typedef TPoolStr<wchar_t> PoolWStr;

    typedef TStrPool<char> StrPool;
    typedef TStrPool<wchar_t> WStrPool;

}   // ::inf

#endif /* !__STRPOOL_H__ */
